package org.apache.osgimaker.editors.model;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.osgimaker.api.IPersistableBundleListModel;
import org.apache.osgimaker.editors.conversions.BundleObjectListConverter;
import org.apache.osgimaker.editors.conversions.CollectionFormatter;
import org.apache.osgimaker.editors.conversions.Converter;
import org.eclipse.jface.text.IDocument;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class AnalyseReportModel implements IPersistableBundleListModel {

	private static final String UTF8 = "UTF-8";

	private final DocumentBuilderFactory domfac = DocumentBuilderFactory
			.newInstance();
	private DocumentBuilder dombuilder = null;

	private List<ClassCycle> classCyclyList = null;
	private List<ClassItem> classList = null;
	private List<ClassCycle> packageCycleList = null;
	private List<ClassItem> packageList = null;
	private static final String CLASSCYCLELIST = "classCycleList";
	private static final String CLASSESLIST = "classesList";

	private static final String[] KNOWN_PROPERTIES = new String[] { CLASSCYCLELIST, CLASSESLIST };

	private final PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(
			this);
	private final Map<String, Bundle> generalInfoChange = new HashMap<String, Bundle>();
	private final Map<String, Bundle> classesInfoChange = new HashMap<String, Bundle>();

	Converter<List<String>, Object> listConverter = new BundleObjectListConverter();
	Converter<List<Bundle>, Object> stringListFormatter = new CollectionFormatter();
	private Properties properties = new Properties();
	
	private boolean classCyclesFlag = false;
	private boolean classListFlag = false;
	@Override
	public void loadFrom(IDocument document) throws IOException {
		if (classCyclyList == null) classCyclyList = new ArrayList<ClassCycle>();
		else classCyclyList.clear();
		if (classList == null) classList = new ArrayList<ClassItem>();
		else classList.clear();
		
		if(packageCycleList == null) packageCycleList = new ArrayList<ClassCycle>();
		else packageCycleList.clear();
		if(packageList == null ) packageList = new ArrayList<ClassItem>();
		else packageList.clear();

		InputStream stream = new ByteArrayInputStream(document.get().getBytes(UTF8));

		try {
			dombuilder = domfac.newDocumentBuilder();
			Document doc = dombuilder.parse(stream);
			Element root = doc.getDocumentElement();
			loadClassCycles(root);
			loadClassesList(root);
			loadPackageCycles(root);
			loadPackageList(root);
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		}

		properties.put(CLASSCYCLELIST, classCyclyList);

		// Fire property changes on all known property names
		for (String prop : KNOWN_PROPERTIES) {
			// null values for old and new forced the change to be fired
			propChangeSupport.firePropertyChange(prop, null, null);
		}
	}
	
	private void getBasicInfo(ClassItem ci, Node classItem){
		Node name = classItem.getAttributes().getNamedItem("name");
		if (name != null)
			ci.setName(name.getNodeValue());
		Node size = classItem.getAttributes().getNamedItem("usedBy");
		if (size != null)
			ci.setUsedBy(Integer.parseInt(size.getNodeValue()));
		Node longestWalk = classItem.getAttributes().getNamedItem("usesInternal");
		if (longestWalk != null)
			ci.setUsesInternal(Integer.parseInt(longestWalk.getNodeValue()));
		Node girth = classItem.getAttributes().getNamedItem("usesExternal");
		if (girth != null)
			ci.setUsesExternal(Integer.parseInt(girth.getNodeValue()));
	}
	
	private void loadPackageList(Element root){
		NodeList bundles = root.getChildNodes();
		if (bundles != null) {
			for (int i = 0; i < bundles.getLength(); i++) {
				Node classCycles = bundles.item(i);
				if (classCycles.getNodeType() == Node.ELEMENT_NODE
						&& classCycles.getNodeName().trim().equals("packages")) {
					NodeList classes = classCycles.getChildNodes();
					for(int j=0; j<classes.getLength(); j++){
						Node classItem = classes.item(j);
						if(classItem.getNodeType() == Node.ELEMENT_NODE && classItem.getNodeName().trim().equals("package")){
							ClassItem ci = new ClassItem();
							getBasicInfo(ci, classItem);
							ci.setClassRefList(getPackageRefList(classItem));
							this.packageList.add(ci);
						}
					}
					break;
				}
			}
		}
		//sort classes
		for(int i=0; i<packageList.size(); i++)
			for(int j=i+1; j<packageList.size(); j++){
				ClassItem ci = packageList.get(i);
				ClassItem cj = packageList.get(j);
				if(cj.getName().compareTo(ci.getName()) < 0){
					ClassItem temp = ci;
					packageList.set(i, cj);
					packageList.set(j, temp);
				}
			}
	}
	
	private void loadClassesList(Element root){
		NodeList bundles = root.getChildNodes();
		if (bundles != null) {
			for (int i = 0; i < bundles.getLength(); i++) {
				Node classCycles = bundles.item(i);
				if (classCycles.getNodeType() == Node.ELEMENT_NODE
						&& classCycles.getNodeName().trim().equals("classes")) {
					classListFlag = true;
					NodeList classes = classCycles.getChildNodes();
					for(int j=0; j<classes.getLength(); j++){
						Node classItem = classes.item(j);
						if(classItem.getNodeType() == Node.ELEMENT_NODE && classItem.getNodeName().trim().equals("class")){
							ClassItem ci = new ClassItem();
							getBasicInfo(ci, classItem);
							ci.setClassRefList(getClassRefList(classItem));
							this.classList.add(ci);
						}
					}
					break;
				}
			}
		}
		//sort classes
		for(int i=0; i<classList.size(); i++)
			for(int j=i+1; j<classList.size(); j++){
				ClassItem ci = classList.get(i);
				ClassItem cj = classList.get(j);
				if(cj.getName().compareTo(ci.getName()) < 0){
					ClassItem temp = ci;
					classList.set(i, cj);
					classList.set(j, temp);
				}
			}
	}
	
	private void getCycleBasicInfo(ClassCycle cc, Node cycle){
		Node name = cycle.getAttributes().getNamedItem("name");
		if (name != null)
			cc.setName(name.getNodeValue());
		Node size = cycle.getAttributes().getNamedItem("size");
		if (size != null)
			cc.setSize(Integer.parseInt(size.getNodeValue()));
		Node longestWalk = cycle.getAttributes().getNamedItem("longestWalk");
		if (longestWalk != null)
			cc.setLongestWalk(Integer.parseInt(longestWalk.getNodeValue()));
		Node girth = cycle.getAttributes().getNamedItem("girth");
		if (girth != null)
			cc.setGirth(Integer.parseInt(girth.getNodeValue()));
	}
	
	private void loadPackageCycles(Element root){
		NodeList bundles = root.getChildNodes();
		if (bundles != null) {
			for (int i = 0; i < bundles.getLength(); i++) {
				Node classCycles = bundles.item(i);
				if (classCycles.getNodeType() == Node.ELEMENT_NODE
						&& classCycles.getNodeName().trim().equals("packageCycles")) {
					NodeList cycles = classCycles.getChildNodes();
					for(int j=0; j<cycles.getLength(); j++){
						Node cycle = cycles.item(j);
						if(cycle.getNodeType() == Node.ELEMENT_NODE && cycle.getNodeName().trim().equals("packageCycle")){
							ClassCycle cc = new ClassCycle();
							getCycleBasicInfo(cc,cycle);
							NodeList children = cycle.getChildNodes();
							for(int k=0; k<children.getLength(); k++){
								Node child = children.item(k);
								if(child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().trim().equals("packages"))
									cc.setClasses(getPackageRefList(child));
								else if(child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().trim().equals("centerPackages"))
									cc.setCenterClasses(getPackageRefList(child));
								else if(child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().trim().equals("bestFragmenters"))
									cc.setBestFragmenters(getPackageRefList(child));
							}
							this.packageCycleList.add(cc);
						}
					}
					break;
				}
			}
		}
	}
	
	private void loadClassCycles(Element root){
		NodeList bundles = root.getChildNodes();
		if (bundles != null) {
			for (int i = 0; i < bundles.getLength(); i++) {
				Node classCycles = bundles.item(i);
				if (classCycles.getNodeType() == Node.ELEMENT_NODE
						&& classCycles.getNodeName().trim().equals("classCycles")) {
					classCyclesFlag = true;
					NodeList cycles = classCycles.getChildNodes();
					for(int j=0; j<cycles.getLength(); j++){
						Node cycle = cycles.item(j);
						if(cycle.getNodeType() == Node.ELEMENT_NODE && cycle.getNodeName().trim().equals("cycle")){
							ClassCycle cc = new ClassCycle();
							getCycleBasicInfo(cc,cycle);
							NodeList children = cycle.getChildNodes();
							for(int k=0; k<children.getLength(); k++){
								Node child = children.item(k);
								if(child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().trim().equals("classes"))
									cc.setClasses(getClassRefList(child));
								else if(child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().trim().equals("centerClasses"))
									cc.setCenterClasses(getClassRefList(child));
								else if(child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().trim().equals("bestFragmenters"))
									cc.setBestFragmenters(getClassRefList(child));
							}
							this.classCyclyList.add(cc);
						}
					}
					break;
				}
			}
		}
	}
	
	private List<ClassRef> getPackageRefList(Node element){
		List<ClassRef> ret = new ArrayList<ClassRef>();
		NodeList refList = element.getChildNodes();
		for(int i=0; i<refList.getLength(); i++){
			Node ref = refList.item(i);
			if(ref.getNodeType() == Node.ELEMENT_NODE && ref.getNodeName().trim().equals("packageRef")){
				ClassRef cr = new ClassRef();
				Node name = ref.getAttributes().getNamedItem("name");
				if (name != null)
					cr.setName(name.getNodeValue());
				Node type = ref.getAttributes().getNamedItem("type");
				if (type != null)
					cr.setType(type.getNodeValue());
				ret.add(cr);
			}
		}
		return ret;
	}
	
	private List<ClassRef> getClassRefList(Node element){
		List<ClassRef> ret = new ArrayList<ClassRef>();
		NodeList refList = element.getChildNodes();
		for(int i=0; i<refList.getLength(); i++){
			Node ref = refList.item(i);
			if(ref.getNodeType() == Node.ELEMENT_NODE && ref.getNodeName().trim().equals("classRef")){
				ClassRef cr = new ClassRef();
				Node name = ref.getAttributes().getNamedItem("name");
				if (name != null)
					cr.setName(name.getNodeValue());
				Node type = ref.getAttributes().getNamedItem("type");
				if (type != null)
					cr.setType(type.getNodeValue());
				ret.add(cr);
			}
		}
		return ret;
	}
	
	public List<String> getPackageCycleList() {
		List<String> cycleList = new ArrayList<String>();
		for(int i=0; i<this.packageCycleList.size(); i++){
			ClassCycle cc = this.packageCycleList.get(i);
			cycleList.add(cc.getName());
		}
		return cycleList;
	}
	
	public List<String> getCycleList() {
		List<String> cycleList = new ArrayList<String>();
		for(int i=0; i<classCyclyList.size(); i++){
			ClassCycle cc = classCyclyList.get(i);
			cycleList.add(cc.getName());
		}
		return cycleList;
	}
	
	public List<String> getPackageList() {
		List<String> classesList = new ArrayList<String>();
		for(int i=0; i<packageList.size(); i++){
			ClassItem cc = packageList.get(i);
			classesList.add(cc.getName());
		}
		return classesList;
	}
	
	public List<String> getClassList() {
		List<String> classesList = new ArrayList<String>();
		for(int i=0; i<classList.size(); i++){
			ClassItem cc = classList.get(i);
			classesList.add(cc.getName());
		}
		return classesList;
	}
	
	public ClassItem getPackageByName(String name){
		for(int i=0; i<packageList.size(); i++){
			ClassItem cc = packageList.get(i);
			if(cc.getName().trim().equals(name.trim())) return cc;
		}
		return null;
	}
	
	public ClassItem getClassByName(String name){
		for(int i=0; i<classList.size(); i++){
			ClassItem cc = classList.get(i);
			if(cc.getName().trim().equals(name.trim())) return cc;
		}
		return null;
	}
	
	public ClassCycle getPackageCycleByName(String name){
		for(int i=0; i<this.packageCycleList.size(); i++){
			ClassCycle cc = packageCycleList.get(i);
			if(cc.getName().trim().equals(name.trim())) return cc;
		}
		return null;
	}
	
	public ClassCycle getClassCycleByName(String name){
		for(int i=0; i<classCyclyList.size(); i++){
			ClassCycle cc = classCyclyList.get(i);
			if(cc.getName().trim().equals(name.trim())) return cc;
		}
		return null;
	}
	

	public void updateBundleGeneralInfo(String bundleId,Bundle bundle){
		generalInfoChange.put(bundleId, bundle);
	}
	public Bundle getChangedBundleGeneralInfo(String bundleId){
		return generalInfoChange.get(bundleId);
	}
	
	public void updateBundleClasses(String bundleId,Bundle bundle){
		classesInfoChange.put(bundleId, bundle);
	}
	
	public Bundle getChangedBundleClasses(String bundleId){
		return classesInfoChange.get(bundleId);
	}

	@Override
	public void saveChangesTo(IDocument document) {
		
	}


	public void addPropertyChangeListener(PropertyChangeListener listener) {
		propChangeSupport.addPropertyChangeListener(listener);
	}

	public void addPropertyChangeListener(String propertyName,
			PropertyChangeListener listener) {
		propChangeSupport.addPropertyChangeListener(propertyName, listener);
	}

	public void removePropertyChangeListener(PropertyChangeListener listener) {
		propChangeSupport.removePropertyChangeListener(listener);
	}

	public void removePropertyChangeListener(String propertyName,
			PropertyChangeListener listener) {
		propChangeSupport.removePropertyChangeListener(propertyName, listener);
	}

	public boolean isClassCyclesFlag() {
		return classCyclesFlag;
	}

	public boolean isClassListFlag() {
		return classListFlag;
	}
	
}
